/*                     __      __  __ __  __                                   *\
**     ________ ___   / /  ___ \ \/ / \ \/ /                                   **
**    / __/ __// _ | / /  / _ | \  /   \  /    scalaxx - scala XML eXtensions  **
**  __\ \/ /__/ __ |/ /__/ __ | /  \   /  \    (c) Normen Mueller et al.       **
** /____/\___/_/ |_/____/_/ | |/_/\_\ /_/\_\   http://kwarc.info/nmueller      **
**                          |/                                                 **
\*                                                                             */

// $Id: XPath.scala 39 2009-11-01 12:10:24Z normen.mueller $

package scala.xml

import org.jaxen.{BaseXPath, DefaultNavigator, NamedAccessNavigator}
import org.jaxen.{JaxenException, UnsupportedAxisException, FunctionCallException}

/** The object <code>XPath</code> provides functions to evaluate XPath 1.0 expressions.
 * 
 * XPath location paths referencing data upside the current context node are retrieved via a seperate hieararchy of parent nodes
 * (cf. <a href="http://code.google.com/p/scala-xml-extensions/wiki/DiscussionSummaries">sclaxx wiki</a>). 
 * 
 * @author Normen M&uuml;ller */
object XPath extends NamedAccessNavigator {
  private var pmap: ParentMap = _
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // The apply methods for convenient construction
  //
  def apply(xp: String, ctx: Document) = query(xp, ctx)
   
  def apply(xp: String, ctx: Node) = query(xp, ctx)
     
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // The select methods
  //
   
  /** Apply <code>xp</code> XPath 1.0 expression on <code>ctx</code> document.
   * 
   * @param xp     the XPath expression
   * @param ctx    the context, say the XML document, to apply the XPath expression on
   * @return the select result sequence
   * 
   * Note: XPath expressions don't return node sequences per se!  Think of requesting attribute values.
   * That's why type of <code>select</code> is <code>Seq[AnyRef]</code>. */
  @throws(classOf[org.jaxen.JaxenException])
  def query(xp: String, ctx: Document): Seq[AnyRef] = query(xp, ctx.docElem)
   
  /** Apply <code>xpath</code> XPath 1.0 expression on <code>ctx</code> node.
   * 
   * @param xpath  the XPath expression
   * @param doc    the context, say the XML fragment to apply the XPath expression on
   * @return the select result sequence
   * 
   * Note: XPath expressions don't return node sequences per se!  Think of requesting attribute values.
   * That's why type of <code>select</code> is <code>Seq[AnyRef]</code>. */
  @throws(classOf[org.jaxen.JaxenException])
  def query(xpath: String, ctx: Node): Seq[AnyRef] = {
    pmap = NodePointerFactory.createParentNodes(ctx)
    parseXPath(xpath) evaluate ctx
  }
   
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////}
  // org.jaxen.Navigator and org.jaxen.NamedAccessNavigator methods
  //
     
  // ----------------------------------------------------------------------
  //     Axis Iterators
  // ----------------------------------------------------------------------

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getChildAxisIterator(java.lang.Object)">Navigator.html#getChildAxisIterator(java.lang.Object)</a> */
  override def getChildAxisIterator(contextNode: AnyRef): It = contextNode match {
    case n: Node => n.child
    //TODO(nrm): evaluate
    //case d: Document => d.children
    //case m: MetaData => m.value
    case _ => Seq.empty
  }
   
  /*Note(nm): I don't need to worry about the prefix here because XPath only iterates by local name and namespace URI. The prefix doesn't matter. */
  def getChildAxisIterator(contextNode: AnyRef, localName: String, namespacePrefix: String, namespaceURI: String): It = contextNode match {
    case n: Node => n.child.filter(c => c.namespace == namespaceURI && c.label == localName)
    //@todo(nrm): evaluate
    //case d: Document => d.children.filter(c => c.namespace == namespaceURI && c.label == localName)
    //case m: MetaData => throw new UnsupportedAxisException("named child axis not supported for attributes")
    case _ => Seq.empty
  }
  
  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getDescendantAxisIterator(java.lang.Object)">Navigator.html#getDescendantAxisIterator(java.lang.Object)</a> */
  override def getDescendantAxisIterator(contextNode: AnyRef): It = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getParentAxisIterator(java.lang.Object)">Navigator.html#getParentAxisIterator(java.lang.Object)</a> */
  override def getParentAxisIterator(contextNode: AnyRef): It = contextNode match {
    case n: Node => Seq.singleton(parent(n))
    case _ => Seq.empty
  }

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getAncestorAxisIterator(java.lang.Object)">Navigator.html#getAncestorAxisIterator(java.lang.Object)</a> */
  override def getAncestorAxisIterator(contextNode: AnyRef): It = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getFollowingSiblingAxisIterator(java.lang.Object)">Navigator.html#getFollowingSiblingAxisIterator(java.lang.Object)</a> */
  override def getFollowingSiblingAxisIterator(contextNode: AnyRef): It = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getPrecedingSiblingAxisIterator(java.lang.Object)">Navigator.html#getPrecedingSiblingAxisIterator(java.lang.Object)</a> */
  override def getPrecedingSiblingAxisIterator(contextNode: AnyRef): It = null
        
  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getFollowingAxisIterator(java.lang.Object)">Navigator.html#getFollowingAxisIterator(java.lang.Object)</a> */
  override def getFollowingAxisIterator(contextNode: AnyRef): It = null
        
  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getPrecedingAxisIterator(java.lang.Object)">Navigator.html#getPrecedingAxisIterator(java.lang.Object)</a> */
  override def getPrecedingAxisIterator(contextNode: AnyRef): It = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getAttributeAxisIterator(java.lang.Object)">Navigator.html#getAttributeAxisIterator(java.lang.Object)</a> */
  override def getAttributeAxisIterator(contextNode: AnyRef): It = contextNode match {
    case n: Node => n.attributes
    case _ => Seq.empty
  }
   
  def getAttributeAxisIterator(contextNode: AnyRef, localName: String, namespacePrefix: String, namespaceURI: String): It = contextNode match {
    case n: Node => n.attributes.filter(a => a match {
      case p: PrefixedAttribute => p.pre == namespacePrefix && p.key == localName && p.getNamespace(n) == namespaceURI
      case u: UnprefixedAttribute => u.key == localName
    })
    case _ => Seq.empty
  }
  
  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getNamespaceAxisIterator(java.lang.Object)">Navigator.html#getNamespaceAxisIterator(java.lang.Object)</a> */
  override def getNamespaceAxisIterator(contextNode: AnyRef): It = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getSelfAxisIterator(java.lang.Object)">Navigator.html#getSelfAxisIterator(java.lang.Object)</a> */
  override def getSelfAxisIterator(contextNode: AnyRef): It = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getDescendantOrSelfAxisIterator(java.lang.Object)">Navigator.html#getDescendantOrSelfAxisIterator</a> */
  override def getDescendantOrSelfAxisIterator(contextNode: AnyRef): It = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getAncestorOrSelfAxisIterator(java.lang.Object)">Navigator.html#getAncestorOrSelfAxisIterator(java.lang.Object)</a> */
  override def getAncestorOrSelfAxisIterator(contextNode: AnyRef): It = null
   
  // ----------------------------------------------------------------------
  //     Extractors
  // ----------------------------------------------------------------------

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getDocument(java.lang.String)">Navigator.html#getDocument(java.lang.String)</a> */
  override def getDocument(uri: String): AnyRef =
    throw new FunctionCallException("document() function not supported")

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getDocumentNode(java.lang.Object)">Navigator.html#getDocumentNode(java.lang.Object)</a> */
  override def getDocumentNode(contextNode: AnyRef): AnyRef = contextNode match {
    case e: Elem => root(e) match {
      case Some(r) => r
      case None => e
    }
    case _ => contextNode
  }

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getParentNode(java.lang.Object)">Navigator.html#getParentNode(java.lang.Object)</a> */
  override def getParentNode(contextNode: AnyRef): AnyRef = contextNode match {
    case n: Node => parent(n)
    case _ => null
  }

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getElementNamespaceUri(java.lang.Object)">Navigator.html#getElementNamespaceUri(java.lang.Object)</a> */
  override def getElementNamespaceUri(element: AnyRef): String = null    

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getElementName(java.lang.Object)">Navigator.html#getElementName(java.lang.Object)</a> */
  override def getElementName(element: AnyRef): String = element match {
    case n: Node => n.label
    case _ => null
  }    

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getElementQName(java.lang.Object)">Navigator.html#getElementQName(java.lang.Object)</a> */
  override def getElementQName(element: AnyRef): String = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getAttributeNamespaceUri(java.lang.Object)">Navigator.html#getAttributeNamespaceUri(java.lang.Object)</a> */
  override def getAttributeNamespaceUri(attr: AnyRef): String = null    

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getAttributeName(java.lang.Object)">Navigator.html#getAttributeName(java.lang.Object)</a> */
  override def getAttributeName(attr: AnyRef): String = attr match {
    case m: MetaData => m.key
    case _ => null
  }

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getAttributeQName(java.lang.Object)">Navigator.html#getAttributeQName(java.lang.Object)</a> */
  override def getAttributeQName(attr: AnyRef): String = null

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getProcessingInstructionTarget(java.lang.Object)">Navigator.html#getProcessingInstructionTarget(java.lang.Object)</a> */
  override def getProcessingInstructionTarget(pi: AnyRef): String = pi match {
    case p: ProcInstr => p.target
    case _ => null
  }

  /** @see <a href="http://jaxen.codehaus.org/apidocs/org/jaxen/Navigator.html#getProcessingInstructionData(java.lang.Object)">Navigator.html#getProcessingInstructionData(java.lang.Object)</a> */
  override def getProcessingInstructionData(pi: AnyRef): String = pi match {
    case p: ProcInstr => p.text
    case _ => null
  }
   
  // ----------------------------------------------------------------------
  //     isXXX testers
  // ----------------------------------------------------------------------
   
  def isElement(contextNode: AnyRef): Boolean = contextNode.isInstanceOf[Elem]
   
  def isText(contextNode: AnyRef): Boolean = contextNode.isInstanceOf[Text]
   
  def isComment(contextNode: AnyRef): Boolean = contextNode.isInstanceOf[Comment]
   
  def isProcessingInstruction(contextNode: AnyRef) = contextNode.isInstanceOf[ProcInstr]
   
  def isDocument(contextNode: AnyRef) = contextNode.isInstanceOf[Document]
   
  def isNamespace(contextNode: AnyRef) = contextNode.isInstanceOf[NamespaceBinding]
   
  def isAttribute(contextNode: AnyRef) = contextNode.isInstanceOf[MetaData]   
   
  // ----------------------------------------------------------------------
  //     String-Value extractors
  // ----------------------------------------------------------------------
     
  override def getCommentStringValue(comment: AnyRef): String = comment match {
    case c: Comment => c.text
    case _ => null
  }

  override def getElementStringValue(element: AnyRef): String = element match {
    case n: Node => n.text
    case _ => null
  }

  override def getAttributeStringValue(attr: AnyRef): String = attr match {
    case m: MetaData => m.value.text
    case _ => null
  }

  override def getNamespaceStringValue(ns: AnyRef): String = ns match {
    case n: NamespaceBinding => n.uri
    case _ => null
  }

  override def getTextStringValue(text: AnyRef): String = text match {
    case t: Text => t.text
    case _ => null
  }

  // ----------------------------------------------------------------------
  //     General utilities
  // ----------------------------------------------------------------------

  override def getNamespacePrefix(ns: AnyRef): String = ns match {
    case n: NamespaceBinding => n.prefix
    case _ => null
  }

  override def translateNamespacePrefixToUri(prefix: String, element: AnyRef): String = null

  override def parseXPath(expr: String) = new BaseXPath(expr, this)
  
  //@todo(nrm): implement
  override def getElementById(contextNode: AnyRef, elementId: String): AnyRef =
    throw new JaxenException("Not yet implemented")
  
  override def getNodeType(node: AnyRef): Short = 0
   
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Parent Axis Utilities
  // @todo(nrm) refactore for general usage!
     
  import scala.collection.immutable.EmptyMap
     
  private case class ParentNode(node: Node, parentNode: Option[ParentNode])
  
  private abstract class NodePointerFactory {
    def createNodePointer(node: Node ): NodePointer
    final def createParentNodes(root: Node): ParentMap =
      _createParentNodes(root, ParentNode(root, None), ParentMap(this, new EmptyMap[NodePointer, ParentNode]()))
     
    private def _createParentNodes(pnode: Node, parentNode: ParentNode, map: ParentMap ): ParentMap = {
      var imap = map
      // go over each child and add its parent nodes
      for { child <- pnode.child
            node <- Some( ParentNode(child, Some(parentNode)) ) }
        imap = _createParentNodes(child, node, ParentMap(imap.factory, imap.map + (map.factory.createNodePointer(child) -> parentNode)) )
      imap
    }
  }
   
  // IdentityNodePointer creates based on the identity of the Node
  private object IdentityNodePointerFactory extends NodePointerFactory {
    def createNodePointer(node: Node) = Pointer[Node](node)
  }
   
  private object NodePointerFactory extends NodePointerFactory {
    def createNodePointer(node: Node) = new Pointer[Node](node) {
      override def hashCode(): int = node.hashCode
    }
  }
  
  private def getRoot(parent: ParentNode): Node =
    if (parent.parentNode.isEmpty) parent.node
    else getRoot( parent.parentNode.get )
  
  private def parent(node: Node): Option[Node] = pmap.map.get(pmap.factory.createNodePointer(node)) match {
    case Some(p) => Some( p.node )
    case _ => None
  }
  
  private def root(node: Node): Option[Node] = {
    val parent = pmap.map.get(pmap.factory.createNodePointer(node))
    if (parent.isEmpty) None else Some( getRoot(parent.get) )
  }

  /*
  def root(node: Node)(implicit map: ParentMap): Option[Node] = {
    def getRoot(parent: ParentNode): Node = if (parent.parentNode.isEmpty) parent.node else getRoot( parent.parentNode.get )
    val parent = map.map.get(map.factory.createNodePointer(node))
     
    if (parent.isEmpty) None
    else Some( getRoot(parent.get) )
  }
  */
  
  private type NodePointer = Pointer[Node]
   
  private case class ParentMap(factory: NodePointerFactory, map: Map[NodePointer, ParentNode]) {
    def apply(node: NodePointer): ParentNode = map(node)
    def get(node: NodePointer): Option[ParentNode] = map.get(node)
  }
  
  private case class Pointer[T](node: T) {
    def apply(): T = node
    override def hashCode(): int = java.lang.System.identityHashCode(node)
  }
   
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // General Utilities
  //

  private implicit def o2seq(obj: AnyRef): Seq[AnyRef] =
    new Seq[AnyRef] {
      val nodes = obj.asInstanceOf[java.util.List[AnyRef]]
      def length = nodes.size
      def elements = new Iterator[AnyRef] {
        val iter = nodes.iterator
        def hasNext: Boolean = iter.hasNext
        def next(): AnyRef = iter.next
      }
      def apply(n: Int): AnyRef = nodes.get(0)
    }
   
  type It = java.util.Iterator[Object]
   
  private implicit def iterable2jiterator[T](c: Iterable[T]) = new IteratorWrapper[T](c.elements)
   class IteratorWrapper[T](underlying: Iterator[T]) extends java.util.Iterator[T] {
     override def next() = underlying.next().asInstanceOf[T]
     override def hasNext = underlying.hasNext
     override def remove() = throw new java.util.NoSuchElementException
  }
}
